/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "ola_pal_network.h"

#include <stdlib.h>
#include "ola_pal_socket.h"
#include "ola_pal_store.h"
#include "ola_pal_os.h"
#include "wifi_device.h"
#include "wifi_hotspot.h"
#include "securec.h"

#define NetworkLog(...) \
    do { \
        printf("[OLA_PAL_NETWORK][%s][%d]", __func__, __LINE__); \
        printf(__VA_ARGS__); \
        printf("\r\n"); \
    } while (0)
#define SCAN_TIMEOUT_SECOND 15
#define MS_PER_SECOND       1000

static int g_netId;
const char *NETID_KEY = "ola_netid";
static bool g_registerFlag = false;
static bool g_scanFlag = false;
static unsigned int g_scanSize = 0;

static void OnWifiConnectionChanged(int state, WifiLinkedInfo* info)
{
    NetworkLog("connection changed [%d]", state);
    return;
}

static void OnWifiScanStateChanged(int state, int size)
{
    NetworkLog("scan state changed [%d] size[%d]", state, size);
    if ((state == WIFI_STATE_AVALIABLE) && (size > 0)) {
        g_scanFlag = true;
        g_scanSize = size;
    }
    return;
}

static void OnHotspotStateChanged(int state)
{
    NetworkLog("hotspot state changed [%d]", state);
    return;
}

static void OnHotspotStaJoin(StationInfo* info)
{
    NetworkLog("hot spot sta join");
    return;
}

static void OnHotspotStaLeave(StationInfo* info)
{
    NetworkLog("hot spot sta leave");
    return;
}

static const WifiEvent WIFI_EVENT = {
    OnWifiConnectionChanged,
    OnWifiScanStateChanged,
    OnHotspotStateChanged,
    OnHotspotStaJoin,
    OnHotspotStaLeave,
};

static int OlaRegisterWifiEvent(void)
{
    if (g_registerFlag) {
        return 0;
    }
    if (RegisterWifiEvent(&WIFI_EVENT) != WIFI_SUCCESS) {
        NetworkLog("register wifi event fail");
        return -1;
    }
    g_registerFlag = true;
    return 0;
}

int OlaSoftApStart(const char *ssid, unsigned int ssidLen)
{
    if ((ssid == NULL) || (ssidLen == 0)) {
        NetworkLog("param error");
        return -1;
    }

    if (IsWifiActive() == WIFI_STATE_AVALIABLE) {
        NetworkLog("sta active, try stop");
        if (DisableWifi() != WIFI_SUCCESS) {
            NetworkLog("stop sta fail");
        }
    }

    if (IsHotspotActive() == WIFI_HOTSPOT_ACTIVE) {
        NetworkLog("ap active, try stop");
        if (DisableHotspot() != WIFI_SUCCESS) {
            NetworkLog("stop ap fail");
        }
    }
    if (OlaRegisterWifiEvent() != 0) {
        NetworkLog("register wifi event error");
        return -1;
    }

    HotspotConfig config;
    (void)memset_s(&config, sizeof(HotspotConfig), 0, sizeof(HotspotConfig));

    if (strncpy_s(config.ssid, WIFI_MAX_SSID_LEN, ssid, ssidLen) != EOK) {
        NetworkLog("strncpy error");
        return -1;
    }
    config.securityType = WIFI_SEC_TYPE_OPEN;

    if (SetHotspotConfig(&config) != WIFI_SUCCESS) {
        NetworkLog("set hotspot config err");
        return -1;
    }

    if (EnableHotspot() != WIFI_SUCCESS) {
        NetworkLog("enable hotspot error");
        return -1;
    }

    return 0;
}

int OlaSoftApStop(void)
{
    if (DisableHotspot() != WIFI_SUCCESS) {
        NetworkLog("disable hotspot error");
        return -1;
    }
    return 0;
}

int OlaGetNetworkState(int *state)
{
    if (state == NULL) {
        NetworkLog("invalid param");
        return -1;
    }
    WifiLinkedInfo info;
    (void)memset_s(&info, sizeof(info), 0, sizeof(info));
    /* 获取不到linkinfo时认为网络未连接 */
    if (GetLinkedInfo(&info) != WIFI_SUCCESS) {
        info.connState = WIFI_DISCONNECTED;
    }

    *state = (info.connState == WIFI_CONNECTED) ? 1 : 0;
    return 0;
}

int OlaGetIpAddr(char *ipAddr, unsigned char len)
{
    if ((ipAddr == NULL) || (len == 0)) {
        NetworkLog("invalid param");
        return -1;
    }

    WifiLinkedInfo info;
    (void)memset_s(&info, sizeof(WifiLinkedInfo), 0, sizeof(WifiLinkedInfo));

    if (GetLinkedInfo(&info) != WIFI_SUCCESS) {
        NetworkLog("get linkinfo error");
        return -1;
    }

    char *ip = OlaInetNtoa(info.ipAddress);
    if (ip == NULL) {
        NetworkLog("ntoa error");
        return -1;
    }
    if (strncpy_s(ipAddr, len, ip, strlen(ip)) != EOK) {
        NetworkLog("strncpy error");
        return -1;
    }
    return 0;
}

int OlaGetMacAddr(unsigned char *mac, unsigned char len)
{
    if ((mac == NULL) || (len == 0)) {
        NetworkLog("invalid param");
        return -1;
    }

    if (GetDeviceMacAddress(mac) != WIFI_SUCCESS) {
        NetworkLog("get device macaddr fail");
        return -1;
    }
    return 0;
}

int OlaClearWifiInfo(void)
{
    unsigned int len = sizeof(g_netId);
    if (OlaStoreGet(NETID_KEY, (unsigned char *)&g_netId, &len) == 0) {
        if (RemoveDevice(g_netId) != WIFI_SUCCESS) {
            NetworkLog("remove old netid error");
            return -1;
        }
    }
    return 0;
}

int OlaSetWifiInfo(const char *ssid, unsigned int ssidLen, const char *pwd, unsigned int pwdLen)
{
    if ((ssid == NULL) || (ssidLen == 0)) {
        NetworkLog("invalid param\r\n");
        return -1;
    }
    /* 删除旧netid */
    (void)OlaClearWifiInfo();

    WifiDeviceConfig config;
    (void)memset_s(&config, sizeof(WifiDeviceConfig), 0, sizeof(WifiDeviceConfig));
    if (strncpy_s(config.ssid, sizeof(config.ssid), ssid, ssidLen) != EOK) {
        NetworkLog("strncpy error");
        return -1;
    }
    if ((pwdLen != 0) && (pwd != NULL)) {
        if (strncpy_s(config.preSharedKey, sizeof(config.preSharedKey), pwd, pwdLen) != EOK) {
            NetworkLog("strncpy error");
            return -1;
        }
        config.securityType = WIFI_SEC_TYPE_PSK;
    } else {
        config.securityType = WIFI_SEC_TYPE_OPEN;
    }
    config.ipType = DHCP;

    if (AddDeviceConfig(&config, &g_netId) != WIFI_SUCCESS) {
        NetworkLog("add config error");
        (void)memset_s(&config, sizeof(WifiDeviceConfig), 0, sizeof(WifiDeviceConfig));
        return -1;
    }
    (void)memset_s(&config, sizeof(WifiDeviceConfig), 0, sizeof(WifiDeviceConfig));

    if (OlaStoreSet(NETID_KEY, (const unsigned char *)&g_netId, sizeof(g_netId)) != 0) {
        NetworkLog("save netid error");
    }
    return 0;
}

static int GetWifiConfigByNetid(WifiDeviceConfig *config, int netId)
{
    unsigned int size = WIFI_MAX_CONFIG_SIZE;
    WifiDeviceConfig wifiConfig[WIFI_MAX_CONFIG_SIZE];
    (void)memset_s(wifiConfig, sizeof(wifiConfig), 0, sizeof(wifiConfig));

    if ((GetDeviceConfigs(wifiConfig, &size) != WIFI_SUCCESS) || (size == 0)) {
        NetworkLog("get wifi config error");
        return -1;
    }
    for (unsigned int i = 0; i < size; ++i) {
        if (wifiConfig[i].netId == netId) {
            if (memcpy_s(config, sizeof(WifiDeviceConfig), &wifiConfig[i], sizeof(WifiDeviceConfig)) != EOK) {
                NetworkLog("memcpy error");
                return -1;
            }
            return 0;
        }
    }
    return -1;
}

static int AdvanceScanWifiBySsid(const WifiDeviceConfig *config)
{
    int scanTimeout = SCAN_TIMEOUT_SECOND;
    WifiScanParams scanParams;
    (void)memset_s(&scanParams, sizeof(scanParams), 0, sizeof(scanParams));
    if (memcpy_s(scanParams.ssid, sizeof(scanParams.ssid), config->ssid, strlen(config->ssid)) != EOK) {
        return -1;
    }
    scanParams.scanType = WIFI_SSID_SCAN;
    scanParams.ssidLen = (char)strlen(config->ssid);

    g_scanSize = 0;
    g_scanFlag = false;

    if (AdvanceScan(&scanParams) != WIFI_SUCCESS) {
        NetworkLog("Wifi advance scan fail");
        return -1;
    }

    while (scanTimeout > 0) {
        OlaSleepMs(MS_PER_SECOND);
        scanTimeout--;
        if (g_scanFlag == true) {
            return 0;
        }
    }

    return -1;
}

static int GetScanWifiResult(const WifiDeviceConfig *config, WifiScanInfo *info)
{
    if (g_scanSize == 0 || g_scanSize > WIFI_SCAN_HOTSPOT_LIMIT) {
        NetworkLog("invalid scan size[%u]", g_scanSize);
        return -1;
    }
    unsigned int size = g_scanSize;
    WifiScanInfo *result = OlaMalloc(sizeof(WifiScanInfo) * size);
    if (result == NULL) {
        NetworkLog("malloc error");
        return -1;
    }
    (void)memset_s(result, sizeof(WifiScanInfo) * size, 0, sizeof(WifiScanInfo) * size);

    int ret = 0;
    do {
        unsigned int resSize = size;
        ret = GetScanInfoList(result, &resSize);
        if ((ret != WIFI_SUCCESS) || (resSize == 0) || (resSize > size)) {
            NetworkLog("get scan list error");
            ret = -1;
            break;
        }

        bool find = false;
        for (unsigned int i = 0; i < resSize; ++i) {
            if (strcmp(result[i].ssid, config->ssid) == 0) {
                NetworkLog("Find target ssid success");
                if (memcpy_s(info, sizeof(WifiScanInfo), &result[i], sizeof(WifiScanInfo)) != 0) {
                    NetworkLog("memcpy_s error");
                    break;
                }
                find = true;
                break;
            }
        }
        if (!find) {
            NetworkLog("not find target ssid");
            ret = -1;
        }
    } while (0);
    OlaFree(result);
    result = NULL;
    return ret;
}

/* 刷新加密方式 */
static int RefreshWifiConfig(WifiDeviceConfig *config, const WifiScanInfo *info)
{
    int scanType;
    if (info->securityType == WIFI_SEC_TYPE_INVALID) {
        /* 扫描出的加密方式非法时根据有无密码使用默认的PSK或OPEN加密方式 */
        scanType = (config->preSharedKey[0] == '\0') ? WIFI_SEC_TYPE_OPEN :
            WIFI_SEC_TYPE_PSK;
    } else {
        scanType = info->securityType;
    }

    if (scanType != config->securityType) {
        NetworkLog("wifi config need refresh pre[%d] cur[%d]", config->securityType, scanType);
        if (RemoveDevice(config->netId) != WIFI_SUCCESS) {
            NetworkLog("remove config error");
            return -1;
        }
        config->netId = 0;
        if (AddDeviceConfig(config, &config->netId) != WIFI_SUCCESS) {
            NetworkLog("add config fail\r\n");
            return -1;
        }
    }
    return 0;
}

static int ScanWifiAndRefreashWifiConfig(WifiDeviceConfig *config)
{
    if (AdvanceScanWifiBySsid(config) != 0) {
        NetworkLog("advance scan error");
        return -1;
    }

    WifiScanInfo info;
    (void)memset_s(&info, sizeof(info), 0, sizeof(info));
    if (GetScanWifiResult(config, &info) != 0) {
        NetworkLog("get scan result error");
        return -1;
    }
    if (RefreshWifiConfig(config, &info) != 0) {
        NetworkLog("refresh config error");
        return -1;
    }

    return 0;
}

int OlaConnectWifi(void)
{
    /* 读取保存的netid */
    unsigned int len = sizeof(g_netId);
    if (OlaStoreGet(NETID_KEY, (unsigned char *)&g_netId, &len) != 0) {
        g_netId = 0;
    }

    if (IsWifiActive() != WIFI_STATE_AVALIABLE) {
        if (EnableWifi() != WIFI_SUCCESS) {
            NetworkLog("enable wifi fail");
            return -1;
        }
    }

    if (OlaRegisterWifiEvent() != 0) {
        NetworkLog("register wifi event error");
        return -1;
    }

    WifiDeviceConfig config;
    (void)memset_s(&config, sizeof(config), 0, sizeof(config));

    if (GetWifiConfigByNetid(&config, g_netId) != 0) {
        NetworkLog("get wifi config error");
        return -1;
    }

    if (ScanWifiAndRefreashWifiConfig(&config) != 0) {
        NetworkLog("scan and refresh wrong");
    }

    if (ConnectTo(config.netId) != WIFI_SUCCESS) {
        (void)memset_s(&config, sizeof(config), 0, sizeof(config));
        NetworkLog("connet to error");
        return -1;
    }
    (void)memset_s(&config, sizeof(config), 0, sizeof(config));
    return 0;
}


int OlaDisconnectWifi(void)
{
    if (Disconnect() != WIFI_SUCCESS) {
        NetworkLog("disconnet to error");
        return -1;
    }
    return 0;
}